﻿#include "hzpch.h"
#include "Scene.h"

#include <box2d/b2_world.h>
#include <box2d/b2_body.h>
#include <box2d/b2_fixture.h>
#include <box2d/b2_polygon_shape.h>

#include "SourceCode/Base/UUID.h"
#include "SourceCode/Scene/Components.h"
#include "SourceCode/Scene/Entity.h"
#include "SourceCode/Scene/ScriptableEntity.h"
#include "SourceCode/Renderer/Renderer2D.h"


namespace Hazal
{
	static b2BodyType RigidBody2DTypeToBox2DBody(SRigidBody2DComponent::EBodyType eBodyType)
	{
		switch (eBodyType)
		{
		case SRigidBody2DComponent::EBodyType::Static:
			return b2BodyType::b2_staticBody;
		case SRigidBody2DComponent::EBodyType::Dynamic:
			return b2BodyType::b2_dynamicBody;
		case SRigidBody2DComponent::EBodyType::Kinematic:
			return b2BodyType::b2_kinematicBody;
		default:
			HZ_ASSERT(false, "Invalid hazal rigid body 2d type!");
			return b2BodyType::b2_staticBody;
		}
	}

	template<typename Component>
	static void CopyComponent(entt::registry& dst, entt::registry& src, const std::unordered_map<CUUID, entt::entity>& mapEntity)
	{
		auto view = src.view<Component>();
		for (auto e : view)
		{
			CUUID id = src.get<SIDComponent>(e).m_id;

			HZ_CORE_ASSERT(mapEntity.find(id) != mapEntity.end(), "entity map is empty!");
			entt::entity entity = mapEntity.at(id);

			auto& component = src.get<Component>(e);
			dst.emplace_or_replace<Component>(entity, component);
		}
	}

	template<typename Component>
	static void CopyComponent(CEntity dst, CEntity src)
	{
		if (src.HasComponent<Component>())
		{
			dst.AddOrReplaceComponent<Component>(src.GetComponent<Component>());
		}
	}
}

namespace Hazal
{
	CScene::CScene()
	{
	}
	
	CScene::~CScene()
	{
		m_registry.view<SNativeScriptComponent>().each([=](auto entity, auto& nsc) 
		{
			if (nsc.m_pInstance)
			{
				nsc.DestroyScript(&nsc);
			}
		});
	}

	Ref<CScene> CScene::Copy(Ref<CScene> pOther)
	{
		Ref<CScene> pNewScene = CreateRef<CScene>();
		
		pNewScene->m_unViewportHeight = pOther->m_unViewportHeight;
		pNewScene->m_unViewportWidth = pOther->m_unViewportWidth;

		std::unordered_map<CUUID, entt::entity> mapEntt;

		auto& srcSceneRegister = pOther->m_registry;
		auto& dstSceneRegister = pNewScene->m_registry;

		/// Create entities in new scene
		auto view = srcSceneRegister.view<SIDComponent>();
		for (auto e : view)
		{
			CUUID id = srcSceneRegister.get<SIDComponent>(e).m_id;
			const std::string& strName = srcSceneRegister.get<STagComponent>(e).m_strTag;

			auto entity = pNewScene->CreateEntityWithUUID(id, strName);
			mapEntt.insert({id, entity});
		}

		/// Add component, except tag component and id component
		CopyComponent<STransformComponent>(dstSceneRegister, srcSceneRegister, mapEntt);
		CopyComponent<SSpriteRendererComponent>(dstSceneRegister, srcSceneRegister, mapEntt);
		CopyComponent<SCameraComponent>(dstSceneRegister, srcSceneRegister, mapEntt);
		CopyComponent<SNativeScriptComponent>(dstSceneRegister, srcSceneRegister, mapEntt);
		CopyComponent<SRigidBody2DComponent>(dstSceneRegister, srcSceneRegister, mapEntt);
		CopyComponent<SCollider2DComponent>(dstSceneRegister, srcSceneRegister, mapEntt);

		return pNewScene;
	}

	void CScene::OnUpdateRuntime(float fTimeSpan)
	{
		/// Update scripts
		{
			if (m_bViewportFoucus)
			{
				m_registry.view<SNativeScriptComponent>().each([=](auto entity, auto& nsc) 
				{
					if (!nsc.m_pInstance)
					{
						nsc.m_pInstance = nsc.InstantiateScript();
						nsc.m_pInstance->m_entity = {entity, this};
						nsc.m_pInstance->OnCreate();
					}
					nsc.m_pInstance->OnUpdate(fTimeSpan);
				});
			}
		}

		/// Physics
		{
			const int32_t nVelocityIteration = 6;
			const int32_t nPositionIteration = 10;
			m_pPhysicsWorld->Step(fTimeSpan, nVelocityIteration, nPositionIteration);

			auto view = m_registry.view<SRigidBody2DComponent>();
			for (auto e : view)
			{
				CEntity entity = { e, this };
				auto& transform = entity.GetComponent<STransformComponent>();
				auto& rigidBody = entity.GetComponent<SRigidBody2DComponent>();

				b2Body* pBody = (b2Body*)rigidBody.m_pRunTimeBody;
				const auto& position = pBody->GetPosition();
				transform.m_vecTranslation.x = position.x;
				transform.m_vecTranslation.y = position.y;
				transform.m_vecRotation.z = pBody->GetAngle();
			}

		}

		/// Render 2D
		CCamera* pMainCamera = nullptr;
		glm::mat4 matTransform;
		{
			auto group = m_registry.view<STransformComponent, SCameraComponent>();
			for (auto entity : group)
			{
				const auto& [transform, camera] = group.get<STransformComponent, SCameraComponent>(entity);

				if (camera.m_bPrimary)
				{ 
					pMainCamera = &camera.m_Camera;
					matTransform = transform.GetTransform();
					break;
				}
			}
		}

		if (pMainCamera)
		{
			CRenderer2D::BeginScene(*pMainCamera, matTransform);

			auto group = m_registry.group<STransformComponent>(entt::get<SSpriteRendererComponent>);
			for (auto entity : group)
			{
				const auto& [transform, sprite] = group.get<STransformComponent, SSpriteRendererComponent>(entity);
				CRenderer2D::DrawSprite(transform.GetTransform(), sprite, (int)entity);
			}

			CRenderer2D::EndScene();
		}
	}

	void CScene::OnUpdateEditor(float fTimeSpan, CEditorCamera& camera)
	{
		CRenderer2D::BeginScene(camera);

		auto group = m_registry.group<STransformComponent>(entt::get<SSpriteRendererComponent>);
		for (auto entity : group)
		{
			const auto& [transform, sprite] = group.get<STransformComponent, SSpriteRendererComponent>(entity);

			CRenderer2D::DrawSprite(transform.GetTransform(), sprite, (int)entity);
		}

		CRenderer2D::EndScene();
	}

	CEntity CScene::CreateEntity(const std::string& strTag /* = std::string()*/)
	{
		return CreateEntityWithUUID(CUUID(), strTag);
	}

	CEntity CScene::CreateEntityWithUUID(uint64_t id, const std::string& strTag)
	{
		CEntity entity = { m_registry.create(), this };

		entity.AddComponent<SIDComponent>(id);
		entity.AddComponent<STransformComponent>();
		auto& tagComponent = entity.AddComponent<STagComponent>();
		tagComponent.m_strTag = strTag.empty() ? "Entity" : strTag;

		return entity;
	}

	void CScene::DestroyEntity(CEntity& entity)
	{
		m_registry.destroy(entity);
	}

	void CScene::OnViewportResize(uint32_t unWidth, uint32_t unHeight)
	{
		m_unViewportWidth = unWidth;
		m_unViewportHeight = unHeight;

		auto view = m_registry.view<SCameraComponent>();
		for (auto& entity : view)
		{
			auto& camera = m_registry.get<SCameraComponent>(entity);
			if (!camera.m_bFixedAspectRadio)
			{
				camera.m_Camera.SetViewportSize(unWidth, unHeight);
			}
		}
	}

	CEntity CScene::GetPrimaryCamera()
	{
		auto view = m_registry.view<SCameraComponent>();
		for (auto& entity : view)
		{
			auto& cc = m_registry.get<SCameraComponent>(entity);
			if (cc.m_bPrimary)
			{
				return { entity, this };
			}
		}

		return {};
	}

	void CScene::OnRunTimeStart()
	{
		m_pPhysicsWorld = new b2World({ 0.0f, -9.8f });
		auto view = m_registry.view<SRigidBody2DComponent>();
		for (auto e : view)
		{
			CEntity entity = {e, this};
			auto& transform = entity.GetComponent<STransformComponent>();
			auto& rigidBody = entity.GetComponent<SRigidBody2DComponent>();

			b2BodyDef bodyDef;
			bodyDef.type = RigidBody2DTypeToBox2DBody(rigidBody.m_eBodyType);
			bodyDef.position.Set(transform.m_vecTranslation.x, transform.m_vecTranslation.y);
			bodyDef.angle = transform.m_vecRotation.z;

			b2Body* pBody = m_pPhysicsWorld->CreateBody(&bodyDef);	///< Create a body
			pBody->SetFixedRotation(rigidBody.m_bFixedRotation);

			rigidBody.m_pRunTimeBody = pBody;

			if (entity.HasComponent<SCollider2DComponent>())
			{
				auto& collider = entity.GetComponent<SCollider2DComponent>();

				b2PolygonShape boxShape;
				boxShape.SetAsBox(collider.m_v2Size.x * transform.m_vecScale.x, collider.m_v2Size.y * transform.m_vecScale.y);

				b2FixtureDef fixtureDef;
				fixtureDef.shape = &boxShape;
				fixtureDef.density = collider.m_fDensity;
				fixtureDef.friction = collider.m_fFriction;
				fixtureDef.restitution = collider.m_fRestitution;
				fixtureDef.restitutionThreshold = collider.m_fRestitutionThreshold;

				pBody->CreateFixture(&fixtureDef);		///< Create a fixture of the body
			}
		}

	}

	void CScene::OnRunTimeStop()
	{
		if (nullptr != m_pPhysicsWorld)
		{
			delete m_pPhysicsWorld;
			m_pPhysicsWorld = nullptr;
		}
	}

	void CScene::DuplicateEntity(CEntity entt)
	{
		CEntity newEntity = CreateEntity(entt.GetTag());

		CopyComponent<STransformComponent>(newEntity, entt);
		CopyComponent<SSpriteRendererComponent>(newEntity, entt);
		CopyComponent<SCameraComponent>(newEntity, entt);
		CopyComponent<SNativeScriptComponent>(newEntity, entt);
		CopyComponent<SRigidBody2DComponent>(newEntity, entt);
		CopyComponent<SCollider2DComponent>(newEntity, entt);
	}

	template<typename T>
	void CScene::OnComponentAdded(CEntity& entity, T& component)
	{
		static_assert(false);
	}

	template<>
	void CScene::OnComponentAdded<STransformComponent>(CEntity& entity, STransformComponent& component)
	{
		// TODO
	}

	template<>
	void CScene::OnComponentAdded<SCameraComponent>(CEntity& entity, SCameraComponent& component)
	{
		component.m_Camera.SetViewportSize(m_unViewportWidth, m_unViewportHeight);
	}

	template<>
	void CScene::OnComponentAdded<STagComponent>(CEntity& entity, STagComponent& component)
	{
		// TODO
	}

	template<>
	void CScene::OnComponentAdded<SNativeScriptComponent>(CEntity& entity, SNativeScriptComponent& component)
	{
		// TODO
	}

	template<>
	void CScene::OnComponentAdded<SSpriteRendererComponent>(CEntity& entity, SSpriteRendererComponent& component)
	{
		// TODO
	}

	template<>
	void CScene::OnComponentAdded<SCircleRendererComponent>(CEntity& entity, SCircleRendererComponent& component)
	{
		// TODO
	}

	template<>
	void CScene::OnComponentAdded<SRigidBody2DComponent>(CEntity& entity, SRigidBody2DComponent& component)
	{
		// TODO
	}

	template<>
	void CScene::OnComponentAdded<SCollider2DComponent>(CEntity& entity, SCollider2DComponent& component)
	{
		// TODO
	}

	template<>
	void CScene::OnComponentAdded<SIDComponent>(CEntity& entity, SIDComponent& component)
	{
		// TODO
	}
}